﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace TestGame.GameLib {
    /// <summary>
    /// Represents a single Blob in the array. The main properties of a Blob are
    /// its position, type, and if it needs to be removed or not. A Blob's main
    /// use is to be added into the BlobArray or controlled by the BlobCombo.
    /// </summary>
    class Blob {
        #region Fields

        /// <summary>
        /// The amount of seconds that DeathTimer will countdown from to remove dead blobs.
        /// </summary>
        const double DECAY_TIME = 0.5;

        /// <summary>
        /// The Blob type (color) that differentiates blobs from another.
        /// </summary>
        byte type;

        /// <summary>
        /// How many blobs of the same type are connected to this Blob
        /// </summary>
        byte connectedBlobs;

        /// <summary>
        /// Used to keep track if the connectedBlobs needs to be updated or not
        /// </summary>
        bool isUpdated;

        /// <summary>
        /// Used to keep track if the Blob is falling or not
        /// </summary>
        bool isLowestBlob;

        /// <summary>
        /// Used to determine if the Blob is marked for deletion
        /// </summary>
        bool removalFlag;

        /// <summary>
        /// A timer that counts down when the Blob will be removed. 
        /// It begins the countdown once willBeRemoved is set to true.
        /// </summary>
        TimeSpan deathTimer;

        /// <summary>
        /// An Array of the different Blob textures
        /// </summary>
        static Texture2D[] blobTextures = new Texture2D[6];

        #endregion

        #region Properties

        /// <summary>
        /// The Graphical size of the tiles
        /// </summary>
        public const int TileSize = 32;

        /// <summary>
        /// The constant that represents a Gray Blob Type.
        /// </summary>
        public const byte Gray = 5;

        /// <summary>
        /// The Grid position of the Blob.
        /// </summary>
        public Vector2 Position;

        /// <summary>
        /// Returns true if the Blob Count has been updated or false if it needs to be updated.
        /// </summary>
        public bool IsUpdated {
            get { return isUpdated; }
            set { isUpdated = value; }
        }

        /// <summary>
        /// Returns if the Blob is the lowest Blob in it's respected column.
        /// </summary>
        public bool IsLowestBlob {
            get { return isLowestBlob; }
            set { isLowestBlob = value; }
        }

        /// <summary>
        /// The integer X position of the Blob
        /// </summary>
        public int X {
            get { return (int)Position.X; } 
        }

        /// <summary>
        /// The integer Y position of the Blob
        /// </summary>
        public int Y {
            get { return (int)Position.Y; }
        }

        /// <summary>
        /// The type of the Blob, ranges from 0 to 5.
        /// </summary>
        public byte Type {
            get { return type; } 
        }

        /// <summary>
        /// The amount of same type Blobs that the Blob is connected to.
        /// Also flags IsUpdated to true if set.
        /// </summary>
        public byte Connected {
            set { 
                connectedBlobs = value;
                this.IsUpdated = true;
            }
            get { return connectedBlobs; }
        }

        /// <summary>
        /// Determines if the Blob is flagged for removal, waiting to actually
        /// be removed.
        /// </summary>
        public bool RemovalFlag {
            get { return removalFlag; }
            set { removalFlag = value; }
        }

        #endregion

        #region Initialization

        /// <summary>
        /// Constructs a default Blob given the type and initial position.
        /// </summary>
        /// <param name="type">The type of the Blob, ranges from 0 to 5</param>
        /// <param name="initialPos">The initial position of the Blob</param>
        public Blob(byte type, Vector2 initialPos) {
            this.type = type;
            Position = initialPos;

            deathTimer = TimeSpan.FromSeconds(DECAY_TIME);
            connectedBlobs = 1;
            isUpdated = false;
            removalFlag = false;
        }

        /// <summary>
        /// Loads the content needed to for all Blob types.
        /// </summary>
        /// <param name="content">Generic content manager</param>
        public static void LoadContent(ContentManager content) {
            blobTextures[0] = content.Load<Texture2D>("red-blob");
            blobTextures[1] = content.Load<Texture2D>("blue-blob");
            blobTextures[2] = content.Load<Texture2D>("green-blob");
            blobTextures[3] = content.Load<Texture2D>("purple-blob");
            blobTextures[4] = content.Load<Texture2D>("yellow-blob");
            blobTextures[5] = content.Load<Texture2D>("gray-blob");
        }

        #endregion

        #region Update Method

        /// <summary>
        /// Updates the Blob, which is only needed when the death timer is 
        /// ticking down.
        /// </summary>
        /// <param name="time">Time elapsed from the last call</param>
        public void Update(GameTime time) {
            if (removalFlag) {
                deathTimer -= time.ElapsedGameTime;
            }
        }

        #endregion

        #region Draw Methods

        /// <summary>
        /// Draws the current Blob based off it's current position.
        /// </summary>
        /// <param name="spriteBatch"></param>
        /// <param name="content"></param>
        public void Draw(SpriteBatch spriteBatch, ContentManager content) {
            Texture2D texture = blobTextures[this.type];
            double sizeRatio = (removalFlag) ? (deathTimer.TotalSeconds / DECAY_TIME) : 1.0;
            int size = (int)(TileSize * sizeRatio);
            float offset = (TileSize - size) / 2.0f;
            Vector2 sizeOffset = new Vector2(offset, offset);

            // DrawSize is used to determine how big or small to draw the blob
            Rectangle drawSize = new Rectangle(0, 0, size, size);

            // DrawPos is used to determine where to draw the blob
            Vector2 drawPos = CalculateDrawPosition(this.Position) + sizeOffset;

            spriteBatch.Draw(texture, drawPos, drawSize, Color.White);

            // Debug drawing
            if (!removalFlag && PlayingArea.DebugMode) {
                SpriteFont font = content.Load<SpriteFont>("gamefont");
                Vector2 textPos = drawPos + new Vector2(12, 0);
                string text = connectedBlobs.ToString();

                if (removalFlag) {
                    text = "!";
                }
                else if (!isUpdated) {
                    text = "?";
                }
                
                spriteBatch.DrawString(font, text, textPos, Color.White);
            }
        }

        /// <summary>
        /// Calculates the drawn Position of the Blob
        /// </summary>
        /// <param name="position">Position of the Blob</param>
        /// <returns></returns>
        public static Vector2 CalculateDrawPosition(Vector2 position) {
            Vector2 bufferOffset = new Vector2(0, -BlobArray.BufferHeight);

            return PlayingArea.GraphicOffset + ((position + bufferOffset) * TileSize);
        }

        /// <summary>
        /// Calculates the drawn Position of the Blob
        /// </summary>
        /// <param name="x">The X coordinate</param>
        /// <param name="y">The Y coordinate</param>
        /// <returns></returns>
        public static Vector2 CalculateDrawPosition(int x, int y) {
            return Blob.CalculateDrawPosition(new Vector2(x, y));
        }

        #endregion

        #region Public Accessors

        /// <summary>
        /// Returns the color related to the current Blob type.
        /// </summary>
        /// <returns></returns>
        public Color FindColor() {
            switch (type) {
                case 0:
                    return Color.FromNonPremultiplied(242, 55, 55, 255);
                case 1:
                    return Color.FromNonPremultiplied(74, 191, 240, 255);
                case 2:
                    return Color.FromNonPremultiplied(159, 206, 49, 255);
                case 3:
                    return Color.FromNonPremultiplied(131, 89, 149, 255);
                case 4:
                    return Color.FromNonPremultiplied(255, 204, 0, 255);
                case 5:
                    return Color.DarkGray;
                default:
                    return Color.White;
            }
        }

        /// <summary>
        /// Rounds the Position of the Blob to the nearest X and Y integer.
        /// </summary>
        public void RoundPosition() {
            int x = (int)Math.Round(Position.X);
            int y = (int)Math.Round(Position.Y);
            this.Position = new Vector2(x, y);
        }

        /// <summary>
        /// Returns if the death timer has counted down and finished.
        /// </summary>
        /// <returns></returns>
        public bool IsDeathTimerOver() {
            return deathTimer <= TimeSpan.Zero;
        }

        /// <summary>
        /// Returns a string representation of the Blob.
        /// </summary>
        /// <returns></returns>
        public override string ToString() {
            string color = "unknown";

            switch (type) {
                case 0:
                    color = "red blob";
                    break;
                case 1:
                    color = "blue blob";
                    break;
                case 2:
                    color = "green blob";
                    break;
                case 3:
                    color = "purple blob";
                    break;
                case 4:
                    color = "yellow blob";
                    break;
                case 5:
                    color = "gray blob";
                    break;
                default:
                    color = "unknown";
                    break;
            }

            return String.Format("[{0} Blob at ({1}, {2})]", color, this.X, this.Y);
        }

        #endregion
    }
}
